﻿#include "GameData.h"
#include "Hook.h"

C_ClientInstance* GameData::clientInstance{};
C_LocalPlayer* GameData::localPlayer{};
C_GameMode* GameData::gameMode{};
C_EntityList* GameData::entityList{};
C_RakNetInstance* GameData::raknetInstance{};
C_HIDController* GameData::hidController{};

std::unordered_set<AABB, AABBHasher> GameData::chestList{};
std::shared_mutex GameData::chestListMutex{};

float GameData::fov{};
int GameData::fps{};
int GameData::frameCount{};
int GameData::cpsLeft{};
int GameData::cpsRight{};
int GameData::leftclickCount{};
int GameData::rightclickCount{};

int64_t GameData::lastUpdate{};
std::u8string GameData::getScreenName{};

bool GameData::shouldTerminateB{};
bool GameData::shouldHideB{};
TextHolder GameData::fakeName{};

fast_io::displayClientMessage_device GameChatBar{};

std::u8string fast_io::sendServiceMessage_device::name{};
std::u8string fast_io::sendServiceMessage_device::xboxid{};
fast_io::sendServiceMessage_device ServiceMessage{};

uintptr_t GameData::retrieveClientInstance() {
	static intptr_t clientInstanceOffset = 0x0;
	if (clientInstanceOffset == 0x0) [[unlikely]] {
		const uintptr_t sigOffset = Utils::Utils::getBase() + 21806886;
		const int offset = *reinterpret_cast<int*>((sigOffset + 3));                                                        // Get Offset from code
		clientInstanceOffset = sigOffset - (uintptr_t)Utils::Utils::helium_HMODULE + offset + /*length of instruction*/ 7;  // Offset is relative
	}

	constexpr auto ReadPtr = [](uintptr_t ptrAddress, std::initializer_list<uintptr_t> ilOffsets) -> uintptr_t {
		for (auto it = ilOffsets.begin(); it != ilOffsets.end(); ++it)
			if ((uintptr_t*)(it + 1) == ilOffsets.end()) [[unlikely]]
				return *reinterpret_cast<uintptr_t*>(ptrAddress + *it);
			else
				ptrAddress = *reinterpret_cast<uintptr_t*>(ptrAddress + *it);
		return 0;
	};
	const auto temp = ReadPtr((uintptr_t)Utils::Utils::helium_HMODULE + clientInstanceOffset, {0x0, 0x0, 0x58});
	clientInstance = reinterpret_cast<C_ClientInstance*>(temp);
	return temp;
}

bool GameData::canUseMoveKeys() {
	/*static*/ const MinecraftGame* const mc = clientInstance->minecraftGame;
	if (mc == nullptr) [[unlikely]]
		return false;
	return mc->canUseKeybinds();
}

bool GameData::isKeyDown(int key) {
	static uintptr_t keyMapOffset = 0x0;
	if (keyMapOffset == 0x0) [[unlikely]] {
		const uintptr_t sigOffset = Utils::Utils::getBase() + 7990851;
		const int offset = *reinterpret_cast<int*>((sigOffset + 3));                                                // Get Offset from code
		keyMapOffset = sigOffset - (uintptr_t)Utils::Utils::helium_HMODULE + offset + /*length of instruction*/ 7;  // Offset is relative
	}
	return *reinterpret_cast<bool*>((uintptr_t)Utils::Utils::helium_HMODULE + keyMapOffset + ((uintptr_t)key * 0x4));
}

bool GameData::isKeyPressed(int key) {
	if (isKeyDown(key)) {
		while (isKeyDown(key))
			Sleep(2);
		return true;
	}
	return false;
}

bool GameData::isRightClickDown() {
	if (hidController == 0) [[unlikely]]
		return false;
	return hidController->rightClickDown;
}

bool GameData::isLeftClickDown() {
	if (hidController == 0) [[unlikely]]
		return false;
	return hidController->leftClickDown;
}

bool GameData::isWheelDown() {
	if (hidController == 0) [[unlikely]]
		return false;
	return hidController->wheelDown;
}

bool GameData::shouldTerminate() {
	return shouldTerminateB;
}

void GameData::terminate() {
	getClientInstance()->minecraft->setTimerSpeed(20.f);
	shouldTerminateB = true;
}

bool GameData::shouldHide() {
	return shouldHideB;
}

void GameData::hide() {
	shouldHideB = !shouldHideB;
}

void GameData::updateGameData(C_GameMode* gm) {
	retrieveClientInstance();
	localPlayer = getLocalPlayer();

	if (localPlayer != nullptr && gm->player == localPlayer) {  // GameMode::tick might also be run on the local server
		gameMode = gm;
		QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER*>(&lastUpdate));
	}
}

void GameData::addChestToList(C_ChestBlockActor* chest) {
	if (chest == nullptr || !chest->isMainSubchest())
		return;
	AABB chestAabb = chest->getFullAABB();
	[[maybe_unused]] auto lock{std::shared_lock<std::shared_mutex>(chestListMutex)};
	if (chestList.count(chestAabb) > 0)
		return;

	chestList.insert(chestAabb);
}

void GameData::EntityList_tick(C_EntityList* list) {
	entityList = list;
}

void GameData::setHIDController(C_HIDController* Hid) {
	hidController = Hid;
}

void GameData::setRakNetInstance(C_RakNetInstance* raknet) {
	raknetInstance = raknet;
}

void GameData::forEachEntity(std::function<void(C_Entity*)> callback) {
	if (localPlayer && localPlayer->level) {
		auto look = std::lock_guard(Hooks::entityList_mutex);

		bool needToclear = false;
		std::set<C_Entity*>::iterator lastEntity{};

		for (auto ent = Hooks::entityList.begin(); ent != Hooks::entityList.end(); ++ent) {
			// clear
			if (needToclear) [[unlikely]]
				Hooks::entityList.erase(lastEntity);
			// check
			__try {
				(*ent)->getNameTag();
				needToclear = false;
			} __except (EXCEPTION_EXECUTE_HANDLER) {
				lastEntity = ent;
				needToclear = true;
				Logger::LogC(LogGrade::error, u8"Invalid Entity Pointer: ", fast_io::mnp::pointervw(*ent));
				Logger::Refresh();
				continue;
			}
			// callback

			__try {
				callback(*ent);
			} __except (EXCEPTION_EXECUTE_HANDLER) {
				Logger::LogC(LogGrade::error, u8"Exception caught! Entity Pointer: ", fast_io::mnp::pointervw(*ent));
				Logger::Refresh();
			}
		}
		if (needToclear) [[unlikely]]
			Hooks::entityList.erase(lastEntity);
	}
}
